/*
 * Copyright (c) 2016, CleverTap
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * - Neither the name of CleverTap nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.clevertap.apns;

import okhttp3.*;
import okio.BufferedSink;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;

/**
 * A wrapper around OkHttp's http client to send out notifications using Apple's HTTP/2 API.
 */
public class OkHttpApnsClient {

    private final String defaultTopic;

    protected final OkHttpClient client;
    private final String gateway;
    private static final MediaType mediaType = MediaType.parse("application/json");


    public OkHttpApnsClient(String defaultTopic, OkHttpClient.Builder builder, String gatewayUrl)
            throws NoSuchAlgorithmException, KeyManagementException {


        final X509TrustManager[] trustAllCerts = new X509TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return new java.security.cert.X509Certificate[]{};
                    }
                }
        };


        final SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

        builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0]);


        builder.hostnameVerifier((hostname, session) -> true);

        client = builder.build();

        this.defaultTopic = defaultTopic;

        this.gateway = gatewayUrl;

    }

    protected final Request buildRequest(ApnsNotification apnsNotification) {
        final String topic =
                apnsNotification.getTopic() != null ? apnsNotification.getTopic() : defaultTopic;
        final String collapseId = apnsNotification.getCollapseId();
        final UUID uuid = apnsNotification.getUuid();
        final long expiration = apnsNotification.getExpiration();
        final ApnsNotification.Priority priority = apnsNotification.getPriority();
        final String pushType = apnsNotification.getPushType();
        Request.Builder rb = new Request.Builder()
                .url(gateway + "/3/device/" + apnsNotification.getToken())
                .post(new RequestBody() {
                    @Override
                    public MediaType contentType() {
                        return mediaType;
                    }

                    @Override
                    public void writeTo(BufferedSink sink) throws IOException {
                        sink.write(apnsNotification.getPayload().getBytes(Constants.UTF_8));
                    }
                })
                .header("content-length",
                        apnsNotification.getPayload().getBytes(Constants.UTF_8).length + "");

        if (topic != null) {
            rb.header("apns-topic", topic);
        }

        if (collapseId != null) {
            rb.header("apns-collapse-id", collapseId);
        }

        if (uuid != null) {
            rb.header("apns-id", uuid.toString());
        }

        if (expiration > -1) {
            rb.header("apns-expiration", String.valueOf(expiration));
        }

        if (priority != null) {
            rb.header("apns-priority", String.valueOf(priority.getCode()));
        }

        if (pushType != null) {
            rb.header("apns-push-type", pushType);
        }

        return rb.build();
    }


    public NotificationResponse push(ApnsNotification apnsNotification) {
        final Request request = buildRequest(apnsNotification);
        Response response = null;

        try {
            response = client.newCall(request).execute();
            return parseResponse(response);
        } catch (Throwable t) {
            return new NotificationResponse(null, -1, null, t);
        } finally {
            if (response != null) {
                response.body().close();
            }
        }
    }


    protected NotificationResponse parseResponse(Response response) throws IOException {
        String contentBody = null;
        int statusCode = response.code();

        NotificationRequestError error = null;

        if (response.code() != 200) {
            error = NotificationRequestError.get(statusCode);
            contentBody = response.body() != null ? response.body().string() : null;
        }

        return new NotificationResponse(error, statusCode, contentBody, null);
    }
}